<?php
abstract class ModelTreeArray extends ModelArray
{
    public $fieldParentId = 'parent_id';
    public $fieldId;
    public $childKey;
    public $branch;
    public $expandAll;
    public $initialCondition;
    public $recursiveCondition;

    public function ModelTreeArray()
    {
        parent::__construct();
        $this->childKey = $this->GetTableName().':childs';
        $this->fieldId = $this->GetPrimaryKey();
    }

    public function FindRecursiveChilds($condition = null, $fields = null)
    {
        $data = $this->Find($condition, $fields);
        $cond = new SqlCondition();
        foreach($data as $key => $row)
        {
            $cond->Add($this->fieldParentId, $row[$this->fieldId], $this->GetField($this->fieldParentId)->type);
            $data[$key][$this->childKey] = $this->FindRecursiveChilds($cond, $fields);
        }
        return $data;
    }

    protected function FindRecursiveChildsByBranch($condition = null, $fields = null, &$level=0, $order = null, $limit = null, &$page = null, &$count = null)
    {
        $init_condition = $this->initialCondition ? $this->initialCondition->AndCondition($condition) : $condition;
        $init_condition->AndCondition($this->recursiveCondition);
        $childs = $this->Find($init_condition, $fields, $order, $limit, $page, $count);
        $this->initialCondition = null;

        $cond = new SqlCondition();
        $cond->AndCondition($condition);
        $child_index = 0;
        $child_count = count($childs);
        foreach($childs as $key => $row)
        {
            $child_index++;
            if ($child_index == 1) $childs[$key]['node_first'] = true;
            if ($child_index == $child_count) $childs[$key]['node_last'] = true;
            $parent_id = $row[$this->fieldId];
            $cond->Add($this->fieldParentId, $parent_id, $this->GetField($this->fieldParentId)->type);
            if (!$this->expandAll && ($level >= count($this->branch) || !array_key_exists($parent_id, $this->branch[$level])))
            {
                $offspring = array();
                $childs[$key]['childs_count'] = $this->Count($cond);
            }
            else
            {
                $level++;
                $childs[$key]['node_open'] = true;
                $offspring = $this->FindRecursiveChildsByBranch($cond, $fields, $level, $order);
                $childs[$key]['childs_count'] = count($offspring);
            }
            if ($childs[$key]['childs_count'] == 0) $childs[$key]['node_leaf'] = true;
            $childs[$key][$this->childKey] = $offspring;
        }
        $level--;
        return $childs;
    }

    public function FindNodes($condition = null, $fields = null, $order = null, $limit = null, &$page = null, &$count = null)
    {
        $fieldParentId = $this->GetTableName().'.'.$this->fieldParentId;
        if (!$condition || (!$condition->IsExisted($this->fieldParentId) && !$condition->IsExisted($fieldParentId)))
        {
            $this->initialCondition = new SqlQuery("{$this->fieldParentId} IS NULL OR {$this->fieldParentId} = 0");
            $this->initialCondition->AndCondition($condition);
        }
        else
            $this->initialCondition = $condition;

        $level = 0;
        return $this->FindRecursiveChildsByBranch(null, $fields, $level, $order, $limit, $page, $count);
    }

    public function LoadTreeBranch($open_childs, $closed_childs = array(), $root = null)
    {
        $this->branch = array();
        if (is_array($open_childs))
        {
            foreach ($open_childs as $open)
            {
                $branch = $this->GetParentNodes($open, $root);
                if (!$this->ContainClosedNode($branch, $closed_childs))
                    $this->branch = TreeSet::MergeBranch($this->branch, $branch);
            }
        }
    }

    protected function ContainClosedNode($branch, $closed_childs)
    {
        foreach ($closed_childs as $closed)
        {
            if (in_array($closed, $branch))
                return true;
        }
        return false;
    }

    protected function GetParentsRecursiveById($id, $fields, $root)
    {
        $cond = new SqlCondition();
        $cond->Add($this->fieldId, $id, $this->GetField($this->fieldId)->type);
        return $this->FindRecursiveParents($cond, $fields, $root);
    }

    public function GetParentNodes($id, $root = null)
    {
        $parents = $this->GetParentsRecursiveById($id, "{$this->fieldId}, {$this->fieldParentId}", $root);
        $tmp = TreeSet::Flat($parents, $this->fieldParentId.':'.$this->GetTableName());
        return array_reverse(Set::Pluck($tmp, $this->fieldId));
    }

    protected function GetChildsRecursive($id, $fields, &$parents = array())
    {
        $childs = $this->GetChilds($id, $fields);
        $parents = array_merge($parents, $childs);
        foreach($childs as $key => $child)
        {
            $this->GetChildsRecursive($child[$this->fieldId], $fields, $parents);
        }
        return $parents;
    }

    protected function GetChilds($parent_id, $fields = null, $orderby = null)
    {
        $cond = new SqlCondition();
        $cond->Add($this->fieldParentId, $parent_id, $this->GetField($this->fieldParentId)->type);
        return $this->Find($cond, $fields, $orderby);
    }

    public function GetAllChildIds($parent_id)
    {
        $childs = $this->GetChildsRecursive($parent_id, $this->fieldId);
        $childs = array_merge(array($parent_id), Set::Pluck($childs, $this->fieldId));
        return $childs;
    }

    public function IsChildOf($id, $parent_id)
    {
        $childs = $this->GetAllChildIds($parent_id);
        return in_array($id, $childs);
    }

    public static function GetInstanceByName($table_name)
    {
        try
        {
            $model = ModelBase::GetInstanceByName($table_name);
        }
        catch (UsingFailedException $ex)
        {
            $model = new ModelTree($table_name);
        }
        return $model;
    }

    public function FindRecursiveParents($condition = null, $fields = null, $root = null)
    {
        $row = $this->FindOne($condition, $fields);
        $fks = $this->GetForeignKeys();
        foreach($fks as $fkfield => $fk)
        {
            if (empty($row[$fkfield])) continue;
            if ($row[$fkfield] == $root) break;

            $fkmodel = ModelBase::GetInstanceByName($fk->table);
            $cond = new SqlCondition();
            $cond->Add($fk->field, $row[$fkfield], $fkmodel->GetField($fk->field)->type);

            $parent = $fkmodel->FindRecursiveParents($cond, $fields, $root);

            $row["$fkfield:{$fk->table}"] = $parent;
        }
        return $row;
    }

    public static function IsNodeFirst($node)
    {
        return isset($node['node_first']);
    }

    public static function IsNodeLeaf($node)
    {
        return isset($node['node_leaf']);
    }

    public static function IsNodeLast($node)
    {
        return isset($node['node_last']);
    }

    public static function IsNodeOpen($node)
    {
        return isset($node['node_open']);
    }

    public function UpdateParentId($id, $parent_id)
    {
        if ($this->IsChildOf($parent_id, $id))
            throw new Exception("Invalid {$this->fieldParentId}");
        else
        {
            $item[$this->fieldId] = $id;
            $item[$this->fieldParentId] = $parent_id;
            return $this->Update($item);
        }
    }

    public function Del($id)
    {
        $pk = $this->GetPrimaryKey();
        $childs = $this->GetChilds($id);
        foreach ($childs as $child)
        {
            $this->Del($child[$pk]);
        }
        return parent::Del($id);
    }
}
?>